package ttsclient

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/astaxie/beego"
	"i-hea.com/ttsc/sdk"
	"io/ioutil"
	"net"
	"net/http"
	"sync"
	"time"

	"os/exec"

	log "github.com/astaxie/beego/logs"
	"github.com/micro/go-micro/errors"
	"i-hea.com/ttsc/cache"
	"i-hea.com/utils/aliyun"
	"strings"
)

type ttsRequest struct {
	Volume float32 `json:"volume"`
	Speed  float32 `json:"speed"`
	Text   string  `json:"text"`
}

type ttsResponse struct {
	Ret   int    `json:"ret"`
	Audio string `json:"audio"`
}

type ttsClient struct {
	client *http.Client
	ttsSrv string
}

type cacheInfo struct {
	file   string
	expair time.Time
}

type ttsMange struct {
	fileId    uint
	fileLock  *sync.RWMutex
	fileCache map[string]*cacheInfo
}

type Result struct {
	retCode int
	err     error
}

var (
	client        ttsClient
	manager       ttsMange
	tencentClient *sdk.Client
	audioDir      string
	ttsCache      *cache.Client
)

func TtsAudioDir() string {
	return audioDir
}

func decodeRadioFile(audioDir, filename, encodeString string) (string, error) {

	decodeBytes, err := base64.StdEncoding.DecodeString(encodeString)
	if err != nil {
		log.Error("std decode error:%v;change raw decode", err.Error())
		if strings.Contains(err.Error(), "illegal") {
			decodeBytes, err = base64.RawStdEncoding.DecodeString(encodeString)
			if err != nil {
				log.Error("task failed:%v;file:%v", err, encodeString)
				return "", err
			}
		} else {
			log.Error("task failed:%v;file:%v", err, encodeString)
			return "", err
		}
	}

	log.Debug(filename, " file size:", len(decodeBytes))

	if len(decodeBytes) == 0 {
		log.Error(filename, "no ecodeBytes", err)
		return "", errors.New("19003", "file data is wrong", 19003)
	}

	file := audioDir + "/" + filename + ".wav"
	//dir := filename + ".wav"

	err = ioutil.WriteFile(file, decodeBytes, 0666)
	if err != nil {
		log.Error(file, "save file failed:", err)
		return "", err
	}

	log.Debug("save file ", file, "success")
	return file, nil
}

func NewTask(name, text string) (string, error) {

	newRequest := ttsRequest{
		Volume: 0.25,
		Speed:  1.0,
		Text:   text,
	}

	req, _ := json.Marshal(newRequest)
	log.Debug("new tts task:", string(req))
	resp, err := send(client.ttsSrv, req)
	if err != nil {
		log.Error("send failed,", err)

		return "", err

	}

	var tts ttsResponse
	err = json.Unmarshal(resp, &tts)
	if err != nil {
		log.Error("json failed,", err)

		return "", err
	}

	log.Debug("recv response: %v", tts.Ret)
	if tts.Ret != 0 {
		log.Error("tts server returned wrong:", tts)
		return "", errors.New("19004", "tts server process failed.", 500)
	}

	file, err := decodeRadioFile(audioDir, name, tts.Audio)
	if err != nil {
		log.Error("save file failed,", err)

		return "", errors.New("19005", "decodeRadioFile failed", 500)
	}

	return file, nil
}

func newHttpClient() *http.Client {

	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				conn, err := net.DialTimeout(netw, addr, time.Second*5)
				if err != nil {
					return nil, err
				}
				//conn.SetDeadline(time.Now().Add(time.Second * 5))
				return conn, nil
			},
			//ResponseHeaderTimeout: time.Second * 5,
			//MaxIdleConnsPerHost: 10,
			//MaxIdleConns:        100,
		},
	}

	return client
}

func send(url string, body []byte) ([]byte, error) {

	reqBody := bytes.NewBuffer(body)

	client := newHttpClient()
	if nil == client {
		return nil, errors.New("19006", "http client create failed", 500)
	}

	req, err := http.NewRequest("POST", url, reqBody)
	if nil != err {

		return nil, err
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json;charset=utf-8")

	len := fmt.Sprintf("%d", len(body))
	req.Header.Set("Content-Length", len)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if 200 != resp.StatusCode {
		log.Error("server response:%v", resp.StatusCode)
		return nil, errors.New("19007", "return status code not 200", 500)
	}

	resbody, err := ioutil.ReadAll(resp.Body)
	return resbody, err
}

func (this *ttsMange) ttsCacheClean() {

	log.Debug("tts cache clean start....")

	lastClean := time.Now()
	for {

		now := time.Now()

		if now.Hour() != 3 || lastClean.Day() == now.Day() {
			time.Sleep(40 * time.Minute)
			continue
		}

		log.Debug("tts cache clean start at %v...", time.Now().String())
		this.fileLock.Lock()
		for k, _ := range this.fileCache {
			log.Debug("delete cache:%v", k)
			delete(this.fileCache, k)
		}
		this.fileLock.Unlock()
		lastClean = now
	}
}

func NewTecnentConvertTask(appId string, args map[string]interface{}) (string, error) {

	ttsStr, ok := args["Text"]
	if !ok {
		log.Debug("[%v]no text tts msg:%v.", appId, args)
		if ttsStr, ok = args["text"]; !ok {
			log.Error("[%v]wrong tts msg:%v.", appId, args)
			return "", errors.New("19001", "ttsText is required", 206)
		} else {
			delete(args, "text")
			args["Text"] = ttsStr
		}
	}

	speedStr, ok := args["speed"]
	if ok {
		delete(args, "speed")
		args["Speed"] = speedStr
	}

	text := ttsStr.(string)
	ttsArgs := tencentClient.GetTtsFlowArgs(args)
	log.Debug("get tts args:volume:%v,speed:%v,voiceType:%v", ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType)
	//从本地缓存中查找
	cachePath, err := getCacheFile(text, ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType)
	if err == nil {

		log.Debug("hit cache:%v file:%v", text, cachePath)
		return cachePath, nil
	} else {
		log.Debug("local hash have no file:%v", err)
	}

	//首先从缓存中查找
	manager.fileLock.RLock()
	cache, ok := manager.fileCache[text]
	manager.fileLock.RUnlock()
	if ok {
		log.Debug("cache %v hit.file:%v", text, cache.file)
		return cache.file, nil

	}

	manager.fileLock.Lock()
	manager.fileId++

	fileName := createCacheFileName(text, ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType)
	//fileName := fmt.Sprintf("TTS_%s_%s_%d", appId, time.Now().Format("0102150405"), manager.fileId)
	log.Debug("new task ,file name:%s", fileName)
	manager.fileLock.Unlock()

	//转换转换tts参数
	file, err := tencentTTS(fileName, args)
	if err != nil {
		log.Error("task failed:", err)
		return "", err
	}

	if len(manager.fileCache) < 20000 {
		manager.fileLock.Lock()
		manager.fileCache[text] = &cacheInfo{file, time.Now()}
		manager.fileLock.Unlock()
	}

	return file, nil
}

func NewTecnentPreConvertTask(args map[string]interface{}) (string, error) {

	ttsStr, ok := args["Text"]
	if !ok {
		log.Debug("no text tts msg.", args)
		if ttsStr, ok = args["text"]; !ok {
			log.Error("wrong tts msg.", args)
			return "", errors.New("19001", "ttsText is required", 206)
		} else {
			delete(args, "text")
			args["Text"] = ttsStr
		}
	}

	text := ttsStr.(string)
	ttsArgs := tencentClient.GetTtsFlowArgs(args)
	log.Debug("get tts args:volume:%v,speed:%v,voiceType:%v,SampleRate：%v",
		ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType, ttsArgs.SampleRate)
	//从redis缓存中查找
	cachePath, err := getCacheFile(text, ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType)
	if err == nil {

		log.Debug("hit cache:%v file:%v", text, cachePath)
		return cachePath, nil
	}

	requestType, err := getArgValueInt("qcloudIntfType", args)
	start := time.Now()
	log.Debug("请求转换:%v ,转换文件名为:%v，modelType:%v  ;", text, cachePath, requestType)
	if err != nil || requestType == 0 {
		//转换转换tts参数
		err = tencentClient.FlowTtsConvert(ttsArgs.SampleRate, cachePath, args)
		if err != nil {
			log.Error("task failed:", err)
			return "", err
		}
	} else {

		fileName := createCacheFileName(text, ttsArgs.Volume, ttsArgs.Speed, ttsArgs.VoiceType)
		_, err = tencentTTS(fileName, args)
		if err != nil {
			log.Error("task failed:", err)
			return "", err
		}
	}

	log.Debug("文字[%v]转换耗时:%v", text, time.Since(start))

	return cachePath, nil
}

func NewAliTTSTask(text, appId string) (string, error) {

	manager.fileLock.RLock()
	cache, ok := manager.fileCache[text]
	manager.fileLock.RUnlock()
	if ok {
		log.Debug("cache %v hit.file:%v", text, cache.file)
		return cache.file, nil

	}

	manager.fileLock.Lock()
	manager.fileId++
	fileName := fmt.Sprintf("%s/TTS_%s_%s_%d.wav", audioDir, appId,
		time.Now().Format("0102150405"), manager.fileId)
	log.Debug("new task ,file name:%s", fileName)
	manager.fileLock.Unlock()

	err := aliyun.NewTTS(fileName, text)
	if err != nil {
		return "", err
	}

	if len(manager.fileCache) < 20000 {
		manager.fileLock.Lock()
		manager.fileCache[text] = &cacheInfo{fileName, time.Now()}
		manager.fileLock.Unlock()
	}

	return fileName, nil
}

func tencentTTS(fileName string, args map[string]interface{}) (string, error) {

	if _, ok := args["SampleRate"]; !ok {
		args["SampleRate"] = 8000
	}

	audioStr, err := tencentClient.TencentTtsNew(args)
	if err != nil {
		log.Error("tencent tts failed: %v,err:%v", args, err)
		return "", err
	}

	file, err := decodeRadioFile(audioDir, fileName, *audioStr)
	if err != nil {
		log.Error("save file failed,", err)

		return "", errors.New("19007", "decodeRadioFile failed", 500)
	}

	return file, nil
}

func slaveSyncRoutine(master string) {

	command := fmt.Sprintf("rsync -raut %v %v", master, audioDir)

	log.Debug("启动tts同步线程,执行命令:%v", command)
	for {

		cmd := exec.Command("bash", "-c", command)
		out, err := cmd.Output()
		if err != nil {
			log.Error("sync from:%v failed:%v .:%v", master, err, string(out))
			time.Sleep(5 * time.Second)
		}

		time.Sleep(time.Second)
	}
}

func Init() {

	client.client = newHttpClient()
	client.ttsSrv = beego.AppConfig.String("ttsSrv")
	audioDir = beego.AppConfig.DefaultString("ttsPath", "/opt/ipcc/tts")
	appId := beego.AppConfig.DefaultInt64("tencentAppId", 1254255210)

	//appKey := beego.AppConfig.String("aliappkey")
	//aliyun.InitTTS(appKey)

	master := beego.AppConfig.String("master")
	secretId := beego.AppConfig.String("tencentSecretId")
	secretKey := beego.AppConfig.String("tencentSecretKey")
	region := beego.AppConfig.DefaultString("tencentServRegion", "ap-shanghai")
	version := beego.AppConfig.DefaultString("tencentApiVersion", "2018-05-22")

	dsid := beego.AppConfig.DefaultString("dsid", time.Now().Format("0405"))

	tencentClient = sdk.NewClient(appId, secretId, secretKey, "TextToVoice",
		region, version, dsid)

	manager.fileId = 0
	manager.fileLock = new(sync.RWMutex)
	manager.fileCache = make(map[string]*cacheInfo)

	if len(master) > 0 {
		go slaveSyncRoutine(master)
	}

	go manager.ttsCacheClean()
}
